package com.brackets;

import com.brackets.ast.AbstractAst;
import com.brackets.ast.FormAst;
import com.brackets.ast.StringAst;
import com.brackets.ast.TermAst;
import com.brackets.exceptions.ParserException;
import com.brackets.stringparser.StringParser;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Dmitry
 */
public class BracketParser {

    public BracketCompiled parse(String codeLine) {
        StringParser parser = new StringParser(codeLine);
        ParserContext context = new ParserContext();
        context.setLayout(parser.getStringLayout());
        context.setParser(parser);
        skipWhitespaceAndComments(parser, false, "No forms found");
        AbstractAst root = parseForm(parser, context);
        skipWhitespaceAndComments(parser, true);
        BracketCompiled compiled = new BracketCompiled();
        return compiled;
    }

    protected void skipWhitespaceAndComments(StringParser parser, boolean canEof) {
        skipWhitespaceAndComments(parser, canEof, null);
    }

    protected void skipWhitespaceAndComments(StringParser parser, boolean canEof, String endErrorMesssage) {
        parser.skipWhitespace();
        if (parser.peek() == '/' && parser.peek(1) == '*') {
            while (true) {
                if (parser.eof()) {
                    if (!canEof) {
                        throw new ParserException(endErrorMesssage);
                    }
                    return;
                }

                if (parser.peek() == '*' && parser.peek(1) == '/') {
                    parser.skip(2);
                    parser.skipWhitespace();
                    return;
                }

                parser.next();
            }
        } else if (parser.peek() == '/' && parser.peek(1) == '/') {
            while (true) {
                if (parser.eof()) {
                    if (!canEof) {
                        throw new ParserException(endErrorMesssage);
                    }
                    return;
                }

                if (parser.peek() == '\n') {
                    parser.next();
                    parser.skipWhitespace();
                    return;
                }

                parser.next();
            }
        }
    }

    protected String readString(StringParser parser, ParserContext context) {
        StringBuilder sb = new StringBuilder(100);
        int position = parser.getCurrentPosition();
        char separator = parser.get();
        if (separator == '"') {
            if (parser.peek(0) == '"' && parser.peek(1) == '"') {//started """ """ string, that is multilined and without any escaping
                parser.skip(2);
                while (true) {
                    if (parser.peek(0) == '"' && parser.peek(1) == '"' && parser.peek(2) == '"') {
                        parser.skip(3);
                        return sb.toString();
                    }
                    char c = parser.get();
                    sb.append(c);
                    if (parser.eof()) {
                        throw new ParserException("Reached end of file while parsing multiline string " + context.positionString(position));
                    }

                }
            }
        }

        if (!(separator == '"' || separator == '\'')) {
            throw new ParserException("Expected string but at position " + context.positionString(parser.getCurrentPosition()) + " found:" + parser.getString(6));
        }

        while (true) {
            char c = parser.get();
            if (c == separator) {
                return sb.toString();
            } else if (c == '\\') {
                c = parser.get();
                switch (c) {
                    case 'n':
                        c = '\n';
                        break;
                    case 't':
                        c = '\t';
                        break;
                    case 'r':
                        c = '\r';
                        break;
                }

                sb.append(c);
            } else {
                sb.append(c);
            }
            if (parser.eof()) {
                throw new ParserException("Expected string end symbol[" + separator + "] but found end of input");
            }
        }
    }

    private TermAst parseTerm(StringParser parser, ParserContext context) {
        int start = parser.getCurrentPosition();
        String value = parser.readUntilWhitespace();
        int length = parser.getCurrentPosition() - start;
        TermAst ast = new TermAst(value);
        ast.setLength(length);
        ast.setStart(start);
        return ast;
    }

    private StringAst parseString(StringParser parser, ParserContext context) {
        int start = parser.getCurrentPosition();
        String value = readString(parser, context);
        int length = parser.getCurrentPosition() - start;
        StringAst ast = new StringAst(value);
        ast.setStart(start);
        ast.setLength(length);
        return ast;
    }
    
    private StringAst parseDigit(StringParser parser, ParserContext context) {
        int start = parser.getCurrentPosition();
        String value = readString(parser, context);
        int length = parser.getCurrentPosition() - start;
        StringAst ast = new StringAst(value);
        ast.setStart(start);
        ast.setLength(length);
        return ast;
    }

    private FormAst innerFormParsing(StringParser parser, ParserContext context) {
        int start = parser.getCurrentPosition();
        char c=parser.get();
        skipWhitespaceAndComments(parser, false, "Reached end of file, but form not closed with [)]");
        List<AbstractAst> asts = new ArrayList<>();
        while (true) {
            skipWhitespaceAndComments(parser, false, "Reached end of file, but form not closed with [)]");
            c = parser.peek();
            if (c == ')') {
                parser.next();
                int length = parser.getCurrentPosition() - start;
                FormAst ast = new FormAst(asts.toArray(new AbstractAst[asts.size()]));
                ast.setStart(start);
                ast.setLength(length);
                return ast;
            }

            AbstractAst ast = parseForm(parser, context);
            if (ast != null) {
                asts.add(ast);
            }
        }
    }

    public AbstractAst parseForm(StringParser parser, ParserContext context) {
        skipWhitespaceAndComments(parser, true);
        char c = parser.peek();
        if (c == '\'' || c == '\"') {
            return parseString(parser, context);
        } else if (c == '(') {
            return innerFormParsing(parser, context);
        } else {
            return parseTerm(parser, context);
        }
    }
}